﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics.Contracts;

using SimpleIR;
using RgnAnalyzer.Domain;

namespace RgnAnalyzer
{
	/// <summary>
	/// States the debugger can be in.
	/// </summary>
	internal enum DebugState { DebugRun, DebugBreakAt, DebugStep, DebugStepInto };

	/// <summary>
	/// Represent the status of a call context.
	/// </summary>
	[Flags]
	internal enum ContextStatus { Clear = 0x0, DependsOnOutputUpdated = 0x1, OnPendingList = 0x2, AnalysisInProgress = 0x4 };

	///////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////
	/// <summary>
	/// This class represents a the analysis memo entry for a single call state
	/// </summary>
	internal sealed class MethodContextState
	{		
		/// <summary>
		/// A unique identifier for this method context.
		/// </summary>
		private readonly int m_contextid;

		/// <summary>
		/// The exact type of the reciver object (for virtual calls or the defining class for statics).
		/// Useful for quick reolving method matches and used to identify recursive call contexts.
		/// </summary>
		private readonly IRType m_contexttoken;

		/// <summary>
		/// If the context is tracking calls within a recursive call cycle.
		/// </summary>
		private readonly bool m_isforrecursivecall;

		/// <summary>
		/// The invoke identity of this context state.
		/// </summary>
		private readonly IRInvokeIdentity m_invokeid;

		/// <summary>
		/// Body that implements the method.
		/// </summary>
		private readonly IRBodyDef m_body; 

		/// <summary>
		/// The ids of all the callee contexts that were used in the last pass through this method.
		/// </summary>
		private readonly HashSet<MethodContextState> m_contextsusedlastpass;

		/// <summary>
		/// The ids of all the callee contexts that have been used in the current pass through this method (or empty if this is not frozen).
		/// </summary>
		private readonly HashSet<MethodContextState> m_contextsusedcurrentpass;

		/// <summary>
		/// If this is frozen waiting for a newly created context this is the id of said context, we need to keep the context live.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
		private MethodContextState m_contextpendingbutnotyetdep;

		/// <summary>
		/// The state of the evaluation of the body.
		/// </summary>
		private readonly MethodEvalState m_bodyEvalState;

		/// <summary>
		/// A map from loop ids to the invariant states at entry to the loop body.
		/// </summary>
		private readonly Dictionary<long, AProgramMemory> m_looptoinvmap;

		//the current in/out arguments
		private AProgramMemory m_inarg;
		private AProgramMemory m_currentprocessin;
		private AProgramMemory m_mergeinapprox;

		private AProgramMemory m_outarg;
		private AProgramMemory m_currentprocessout;

		/// <summary>
		/// After the completetion of the heap info this will contain the output of the local 
		/// analysis with the escape identity info.
		/// </summary>
		private AProgramMemory m_calleescprocresult;

		/// <summary>
		/// Number of times we have re-analyzed the body.
		/// </summary>
		private int m_numberOfAnalysisPasses;

		/// <summary>
		/// The debug state of the method correspoding to this analysis.
		/// </summary>
		private DebugState m_csdbg;

		/// <summary>
		/// The current analysis state of this call.
		/// </summary>
		private ContextStatus m_currentContextStatus;

		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			Contract.Invariant(this.m_contexttoken != null);
			Contract.Invariant(this.m_invokeid != null);
			Contract.Invariant(this.m_body != null);
			Contract.Invariant(this.m_contextsusedlastpass != null);
			Contract.Invariant(this.m_contextsusedcurrentpass != null);
			Contract.Invariant(this.m_bodyEvalState != null);
		}

		/// <summary>
		/// Make a CallStateMapEntry for normal project/extend memo analysis.
		/// </summary>
		internal MethodContextState(IRInvokeIdentity invid, IRBodyDef body, AProgramMemory inarg, int mcsctr, bool isinsiderecursive, IRType ctxttoken)
		{
			Contract.Requires(invid != null);
			Contract.Requires(body != null);
			
			this.m_contextid = mcsctr;
			this.m_contexttoken = ctxttoken;
			this.m_isforrecursivecall = isinsiderecursive;
			
			this.m_invokeid = invid;
			this.m_body = body;
			this.m_contextsusedlastpass = new HashSet<MethodContextState>();
			this.m_contextsusedcurrentpass = new HashSet<MethodContextState>();
			this.m_contextpendingbutnotyetdep = null;

			this.m_bodyEvalState = new MethodEvalState();
			this.m_looptoinvmap = new Dictionary<long, AProgramMemory>();

			this.m_inarg = inarg;
			this.m_currentprocessin = null;
			this.m_mergeinapprox = null;

			this.m_currentprocessout = null;
			this.m_outarg = null;

			this.m_calleescprocresult = null;

			this.m_csdbg = RgnAnalyzer.DebugState.DebugRun;
			this.m_currentContextStatus = ContextStatus.Clear;
		}

		public override string ToString()
		{
			StringBuilder sb = new StringBuilder();

			sb.Append(this.MethodOfCallState.ToString() + "[" + this.m_contextid + (this.m_isforrecursivecall ? "-rec]" : "]"));

			bool frozen = (this.CurrentStatus & ContextStatus.AnalysisInProgress) != ContextStatus.Clear;
			sb.Append((frozen ? ", Frzn" : ""));

			return sb.ToString();
		}

		/// <summary>
		/// Add the figs in this in/out pair to the display lists.
		/// </summary>
		internal void LoadPairForDisplayOrAnalysis(List<Tuple<AProgramMemory, AProgramMemory, AProgramMemory>> modellist)
		{
			Contract.Requires(modellist != null);

			modellist.Add(new Tuple<AProgramMemory, AProgramMemory, AProgramMemory>(this.m_inarg, this.m_calleescprocresult, this.m_outarg));
		}

		/// <summary>
		/// Get the figs models in this in/out pair.
		/// </summary>
		internal Tuple<AProgramMemory, AProgramMemory> GetInputOutputPairForCompleteInfo()
		{ return new Tuple<AProgramMemory, AProgramMemory>(this.m_inarg, this.m_calleescprocresult); }

		/// <summary>
		/// Get copy of output state for project/extend memo case.
		/// </summary>
		internal AProgramMemory GetCopyOfOutputStateApplyMaps(CallStateSubMap csm)
		{
			Contract.Requires(csm != null);

			if((csm.OutCase != MatchCase.SubsumeAlter) & (this.m_outarg != null))
			{
				AProgramMemory res = this.m_outarg.CopyMemoryState();
				res.MapIntoResForFinalResult(csm);
				res.CleanInfeasibleNodes();
				res.CheckConsistent();
				return res;
			}
			else
			{
				GlobalAnalysisState.Display__MemoizedPartialApproxUsed();

				if(this.m_outarg == null)
				{
					AProgramMemory respa = new AProgramMemory();
					respa.SetInfeasable();
					return respa;
				}
				else
				{
					AProgramMemory respa = this.m_outarg.CopyMemoryState();
					respa.MapIntoResForFinalResult(csm);
					respa.CleanInfeasibleNodes();
					respa.CheckConsistent();
					return respa;
				} 
			}
		}

		/// <summary>
		/// Get a copy of the input state for comparison in memo table lookup, DON'T MODIFY THIS.
		/// </summary>
		internal AProgramMemory GetInputContextForCompare()
		{
			return this.m_inarg;
		}

		/// <summary>
		/// Get the state we need to compute for this pass over the method body.
		/// </summary>
		internal AProgramMemory GetCopyOfInputStateForCompute()
		{
			Contract.Assert(this.m_currentprocessout == null && (this.m_mergeinapprox != null || this.m_inarg != null), "bad setup for call analysis");

			this.CurrentStatus &= ~(ContextStatus.DependsOnOutputUpdated);

			if(this.m_mergeinapprox != null)
			{
				this.m_currentprocessin = this.m_mergeinapprox;
				this.m_mergeinapprox = null;

				this.m_currentprocessin.EnsureUniqueIdentitiesAndExternalRefsPerNode();

				////
				AProgramMemory respa = null;
				if(this.m_outarg != null)
				{
					respa = this.m_outarg.CopyMemoryState();
					CallStateSubMap csm = new CallStateSubMap();

					AMemEqPartition pmcs = AProgramMemory.PMemoryMergeCallsPartition(this.m_inarg, this.m_currentprocessin);
					this.m_inarg.BuildCallStateMapForSubsume_OptionalCheckSubsume(this.m_currentprocessin, pmcs, csm, false);
					
					respa.MapIntoResForFinalResult(csm);
					respa.CleanInfeasibleNodes();
					respa.CheckConsistent(); 
				}

				this.m_inarg = m_currentprocessin.CopyMemoryState();
				this.m_outarg = respa;
				////

				return this.m_currentprocessin.CopyMemoryState();
			}
			else
			{
				return this.m_inarg.CopyMemoryState();
			}
		}

		/// <summary>
		/// The unique id associated with this context.
		/// </summary>
		public int ContextID
		{ get { return this.m_contextid; } }

		/// <summary>
		/// The exact type of the reciver object (for virtual calls or the defining class for statics).
		/// Useful for quick reolving method matches and used to identify recursive call contexts.
		/// </summary>
		internal IRType ContextToken
		{ get { return this.m_contexttoken; } }

		/// <summary>
		/// Return true if this context is for a call that takes place inside a recursive call SCC.
		/// </summary>
		internal bool IsInteralRecursiveCallContext
		{ get { return this.m_isforrecursivecall; } }

		internal HashSet<MethodContextState> ContextIDsUsedLastPass
		{ get { return this.m_contextsusedlastpass; } }

		internal HashSet<MethodContextState> ContextIDsUsedCurrentPass
		{ get { return this.m_contextsusedcurrentpass; } }

		internal MethodContextState ContextIDPendingNotYetDep
		{ set { this.m_contextpendingbutnotyetdep = value; } }

		internal IRInvokeIdentity MethodOfCallState
		{ get { return this.m_invokeid; } }

		internal IRBodyDef CallStateMethodBody
		{ get { return this.m_body; } }

		internal MethodEvalState BodyEvalState
		{ get { return this.m_bodyEvalState; } }

		/// <summary>
		/// Get and set the state of the debug analyzer.
		/// </summary>
		internal DebugState DebugState
		{
			get { return this.m_csdbg; }
			set { this.m_csdbg = value; }
		}

		internal ContextStatus CurrentStatus
		{
			get { return this.m_currentContextStatus; }
			set { this.m_currentContextStatus = value; }
		}

		internal int EvalPassCount
		{ 
			get { return this.m_numberOfAnalysisPasses; } 
			set { this.m_numberOfAnalysisPasses = value; } 
		}

		internal bool IsNewInputPending
		{ get { return this.m_mergeinapprox != null; } }

		internal Dictionary<long, AProgramMemory> LoopToEntryInvStateMap
		{ get { return this.m_looptoinvmap; } }

		////////////////////////////////////////////////////////////////////////
		//Support for fixpoint analysis

		/// <summary>
		/// When perfoming the semi-context sensitive analysis on recursive calls, if the current input approx. 
		/// did not subsume the new call context, merge the new context into the next approx. value.
		/// </summary>
		internal void UpdateInApproxOnNotSubsumed(AProgramMemory opm)
		{
			Contract.Requires(opm != null);

			GlobalAnalysisState.Display__PrintAnalysisResultsInputChanged();

			if(this.m_mergeinapprox != null)
				this.m_mergeinapprox = AProgramMemory.PMemoryMergeCallsEntry(this.m_mergeinapprox, opm);
			else
				this.m_mergeinapprox = AProgramMemory.PMemoryMergeCallsEntry(this.m_inarg, opm);
		}

		//at return call this to merge return state into the new current return approx, takes possestion of rpm
		internal void UpdateCurrentApprox(AProgramMemory rpm)
		{
			Contract.Assert(this.m_currentprocessout == null, "There should only be one return statement in a method!!!");

			if(rpm != null)
			{
				if(GlobalAnalysisState.TProg.CallGraph.IsCallInSCC_StaticCallGraph(this.m_invokeid))
					rpm.NormalizeAtRecursiveCallExit();
				else
					rpm.NormalizeAtSimpleCallExit();

				this.m_currentprocessout = rpm;
			}
		}

		/// <summary>
		/// Compute if the result changed for the given approx, return true if dependant callStates as needing to recompute.
		/// </summary>
		internal bool UpdateReturnContextNoteIfChanged()
		{
			Contract.Assert((this.m_currentprocessin != null) | (this.m_inarg != null));
			
			bool changed;
			if(this.m_currentprocessout == null)
				changed = false;
			else 
				changed = this.m_outarg == null || !AProgramMemory.EquivOnExit(this.m_calleescprocresult, this.m_currentprocessout, null);
			
			if(this.m_currentprocessin != null)
			{
				this.m_inarg = this.m_currentprocessin;
				this.m_currentprocessin = null;
			}

			this.m_contextsusedlastpass.Clear();
			this.m_contextsusedlastpass.UnionWith(this.m_contextsusedcurrentpass);

			this.m_contextsusedcurrentpass.Clear();
			this.m_contextpendingbutnotyetdep = null;

			if(changed)
			{
				this.m_calleescprocresult = this.m_currentprocessout.CopyMemoryState();

				this.m_outarg = this.m_currentprocessout;
				this.m_outarg.UpdateReturnContextWithEscapeIdentities();
			}
			this.m_currentprocessout = null;

			return changed;
		}

		/// <summary>
		/// After doing the final pass relate the escape identities in the computed model to the fresh escape identities 
		/// we propigate from the output state.
		/// </summary>
		internal void FinalPassExtractCalleeScopeEscapeToCallerScopeEscapeAndUsedContextUpdate()
		{
			this.m_contextsusedlastpass.Clear();
			this.m_contextsusedlastpass.UnionWith(this.m_contextsusedcurrentpass);

			this.m_contextsusedcurrentpass.Clear();
			this.m_contextpendingbutnotyetdep = null;

			this.m_currentprocessout = null;

			if(this.m_calleescprocresult != null)
				AProgramMemory.FinalPassExtractCalleeScopeEscapeToCallerScopeEscape(this.m_calleescprocresult, this.m_outarg);
		}
	}
}
